package cn.hamster3.mc.plugin.ball.common.api;

import cn.hamster3.mc.plugin.ball.common.config.BallConfig;
import cn.hamster3.mc.plugin.ball.common.data.BallLocation;
import cn.hamster3.mc.plugin.ball.common.data.BallMessage;
import cn.hamster3.mc.plugin.ball.common.entity.BallPlayerInfo;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerType;
import cn.hamster3.mc.plugin.ball.common.event.BallActions;
import cn.hamster3.mc.plugin.ball.common.event.message.MessageSentEvent;
import cn.hamster3.mc.plugin.ball.common.event.operate.*;
import cn.hamster3.mc.plugin.ball.common.event.server.ServerOfflineEvent;
import cn.hamster3.mc.plugin.ball.common.listener.BallCommonListener;
import cn.hamster3.mc.plugin.ball.common.listener.BallDebugListener;
import cn.hamster3.mc.plugin.ball.common.listener.BallRedisListener;
import cn.hamster3.mc.plugin.ball.common.thread.LockUpdateThread;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import cn.hamster3.mc.plugin.core.common.config.ConfigSection;
import cn.hamster3.mc.plugin.core.common.data.DisplayMessage;
import cn.hamster3.mc.plugin.core.common.util.CoreUtils;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
import lombok.Getter;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import redis.clients.jedis.Jedis;

import javax.sql.DataSource;
import java.sql.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

@SuppressWarnings("unused")
@Getter
public abstract class BallAPI {
    /**
     * API 使用的通信频道
     */
    public static final String BALL_CHANNEL = "HamsterBall";
    /**
     * API 使用的玩家信息更新通信频道
     */
    public static final String PLAYER_INFO_CHANNEL = "HamsterBall:PlayerInfo";

    /**
     * API 实例
     */
    @Getter
    protected static BallAPI instance;

    @NotNull
    private final BallConfig ballConfig;
    @NotNull
    private final DataSource datasource;
    @NotNull
    private final BallServerInfo localServerInfo;

    @NotNull
    private final EventBus eventBus;

    @NotNull
    private final Map<String, BallServerInfo> allServerInfo;
    @NotNull
    private final Map<UUID, BallPlayerInfo> allPlayerInfo;

    @NotNull
    private final Jedis redisSub;
    @Nullable
    private ScheduledFuture<?> lockUpdater;

    public BallAPI(@NotNull ConfigSection config, BallServerType type) {
        ConfigSection serverInfoConfig = config.getSection("server-info");
        if (serverInfoConfig == null) {
            throw new IllegalArgumentException("配置文件中未找到 server-info 节点");
        }
        localServerInfo = new BallServerInfo(serverInfoConfig, type);
        ConfigSection section = config.getSection("datasource");
        if (section != null) {
            getLogger().info("启用仓鼠球自定义数据库连接池");
            datasource = CoreUtils.getDataSource(section);
        } else {
            getLogger().info("复用 HamsterCore 的数据库连接池");
            datasource = CoreAPI.getInstance().getDataSource();
        }
        ballConfig = new BallConfig(config);
        eventBus = new AsyncEventBus("HamsterBall - EventBus", CoreAPI.getInstance().getExecutorService());
        eventBus.register(BallCommonListener.INSTANCE);
        allServerInfo = new ConcurrentHashMap<>();
        allPlayerInfo = new ConcurrentHashMap<>();
        redisSub = CoreAPI.getInstance().getJedisPool().getResource();
        getLogger().info("频道前缀: " + ballConfig.getChannelPrefix());
        getLogger().info("启用子服更新玩家状态: " + ballConfig.isGameServerUpdatePlayerInfo());
        if (ballConfig.isGameServerUpdatePlayerInfo()) {
            getLogger().info("仅加载以下代理服入口的玩家信息: " + ballConfig.getLoadPlayerInfoFilter());
        }
        if (ballConfig.isDebug()) {
            getLogger().warning("已启用调试模式");
            eventBus.register(BallDebugListener.INSTANCE);
        }
    }

    protected void enable() throws SQLException, InterruptedException {
        try (Jedis jedis = CoreAPI.getInstance().getJedisPool().getResource()) {
            String key = "HamsterBall:ServerInfo:" + localServerInfo.getId();
            if (jedis.exists(key)) {
                throw new IllegalStateException("已经有一个服务器占用了该 ID");
            }
            jedis.hset(key, "id", localServerInfo.getId());
            jedis.hset(key, "name", localServerInfo.getName());
            jedis.hset(key, "type", localServerInfo.getType().name());
            jedis.hset(key, "host", localServerInfo.getHost());
            jedis.hset(key, "port", String.valueOf(localServerInfo.getPort()));
            jedis.expire(key, 180);
            lockUpdater = CoreAPI.getInstance().getScheduledService().scheduleAtFixedRate(LockUpdateThread.INSTANCE, 1, 1, TimeUnit.MINUTES);
            for (String serverInfoKey : jedis.keys("HamsterBall:ServerInfo:*")) {
                BallServerInfo info = new BallServerInfo(
                        jedis.hget(serverInfoKey, "id"),
                        jedis.hget(serverInfoKey, "name"),
                        BallServerType.valueOf(jedis.hget(serverInfoKey, "type")),
                        jedis.hget(serverInfoKey, "host"),
                        Integer.parseInt(jedis.hget(serverInfoKey, "port"))
                );
                allServerInfo.put(info.getId(), info);
            }
        }
        try (Connection connection = getDatasource().getConnection()) {
            try (Statement statement = connection.createStatement()) {
                statement.execute("CREATE TABLE IF NOT EXISTS `hamster_ball_player_info`(" +
                        "`uuid` CHAR(36) PRIMARY KEY," +
                        "`name` VARCHAR(16) NOT NULL," +
                        "`game_server` VARCHAR(32) NOT NULL," +
                        "`proxy_server` VARCHAR(32) NOT NULL," +
                        "`online` BOOLEAN NOT NULL" +
                        ") CHARSET utf8mb4;");
                statement.execute("CREATE TABLE IF NOT EXISTS `hamster_ball_cached_message`(" +
                        "`uuid` CHAR(36) NOT NULL," +
                        "`message` TEXT NOT NULL" +
                        ") CHARSET utf8mb4;");
            }
            if (getBallConfig().isGameServerUpdatePlayerInfo()) {
                try (Statement statement = connection.createStatement()) {
                    try (ResultSet set = statement.executeQuery(String.format(
                            "SELECT * FROM `hamster_ball_player_info` WHERE `proxy_server` IN ('%s');",
                            String.join("', '", getBallConfig().getLoadPlayerInfoFilter())
                    ))) {
                        while (set.next()) {
                            UUID uuid = UUID.fromString(set.getString("uuid"));
                            allPlayerInfo.put(uuid, new BallPlayerInfo(uuid,
                                    set.getString("name"),
                                    set.getString("game_server"),
                                    set.getString("proxy_server"),
                                    set.getBoolean("online")
                            ));
                        }
                    }
                }
            } else {
                try (PreparedStatement statement = connection.prepareStatement(
                        "SELECT * FROM `hamster_ball_player_info`;"
                )) {
                    try (ResultSet set = statement.executeQuery()) {
                        while (set.next()) {
                            UUID uuid = UUID.fromString(set.getString("uuid"));
                            allPlayerInfo.put(uuid, new BallPlayerInfo(uuid,
                                    set.getString("name"),
                                    set.getString("game_server"),
                                    set.getString("proxy_server"),
                                    set.getBoolean("online")
                            ));
                        }
                    }
                }
            }
        }
        getLogger().info("从数据库中加载了 " + allServerInfo.size() + " 条服务器信息");
        getLogger().info("从数据库中加载了 " + allPlayerInfo.size() + " 条玩家信息");
        subscribeIgnorePrefix(BALL_CHANNEL);
    }

    protected void disable() throws SQLException, InterruptedException {
        sendBallMessage(BallAPI.BALL_CHANNEL, new BallMessage(
                BallActions.ServerOffline.name(), new ServerOfflineEvent(getLocalServerInfo())
        ), false, true);
        if (lockUpdater != null) {
            lockUpdater.cancel(true);
            lockUpdater = null;
            try (Jedis jedis = CoreAPI.getInstance().getJedisPool().getResource()) {
                String key = "HamsterBall:ServerInfo:" + localServerInfo.getId();
                jedis.del(key);
            }
        }
        try (Connection connection = getDatasource().getConnection()) {
            try (PreparedStatement statement = connection.prepareStatement(
                    "UPDATE `hamster_ball_player_info` SET `online`=false WHERE `game_server`=? OR `proxy_server`=?"
            )) {
                statement.setString(1, getLocalServerId());
                statement.setString(2, getLocalServerId());
                statement.executeUpdate();
            }
        }
        redisSub.close();
    }

    /**
     * 判断该服务器信息是否为本服
     *
     * @param info 服务器信息
     * @return true 代表该服务器信息是本服服务器
     */
    public boolean isLocalServer(@NotNull BallServerInfo info) {
        return getLocalServerInfo().equals(info);
    }

    /**
     * 判断该服务器信息是否为本服
     *
     * @param serverID 服务器ID
     * @return true 代表该服务器信息是本服服务器
     */
    public boolean isLocalServer(@Nullable String serverID) {
        return getLocalServerId().equalsIgnoreCase(serverID);
    }

    /**
     * 给服务器的在线玩家广播一条消息
     *
     * @param message 消息
     */
    public void broadcastPlayerMessage(@NotNull String message) {
        broadcastPlayerMessage(new DisplayMessage().setMessage(message));
    }

    /**
     * 给服务器的在线玩家广播一条消息
     *
     * @param message 消息
     */
    public void broadcastPlayerMessage(@NotNull DisplayMessage message) {
        sendBallMessage(BALL_CHANNEL, new BallMessage(
                getLocalServerId(), null, BallServerType.PROXY,
                BallActions.BroadcastPlayerMessage.name(),
                CoreAPI.getInstance().getGson().toJsonTree(new BroadcastPlayerMessageEvent(message))
        ), false);
    }

    /**
     * 强制控制台执行命令
     *
     * @param type     执行对象的服务端类型
     * @param serverID 执行对象的 ID
     * @param command  命令内容
     */
    public void dispatchConsoleCommand(@Nullable BallServerType type, @Nullable String serverID, @NotNull String command) {
        sendBallMessage(BALL_CHANNEL, new BallMessage(
                getLocalServerId(), null, BallServerType.GAME,
                BallActions.DispatchConsoleCommand.name(),
                CoreAPI.getInstance().getGson().toJsonTree(new DispatchConsoleCommandEvent(type, serverID, command))
        ), false);

    }

    /**
     * 强制玩家执行命令
     *
     * @param type    执行对象的服务端类型
     * @param uuid    执行对象的 UUID
     * @param command 命令内容
     */
    public void dispatchPlayerCommand(@Nullable BallServerType type, @Nullable UUID uuid, @NotNull String command) {
        sendBallMessage(BALL_CHANNEL, new BallMessage(
                getLocalServerId(), null, BallServerType.GAME,
                BallActions.DispatchPlayerCommand.name(),
                CoreAPI.getInstance().getGson().toJsonTree(new DispatchPlayerCommandEvent(type, uuid, command))
        ), false);
    }

    /**
     * 踢出玩家
     *
     * @param uuid   玩家
     * @param reason 原因
     */
    public void kickPlayer(@NotNull UUID uuid, @NotNull String reason) {
        kickPlayer(uuid, Component.text(reason));
    }

    /**
     * 踢出玩家
     *
     * @param uuid   玩家
     * @param reason 原因
     */
    public void kickPlayer(@NotNull UUID uuid, @NotNull Component reason) {
        sendBallMessage(BALL_CHANNEL, new BallMessage(
                getLocalServerId(), null, BallServerType.PROXY,
                BallActions.KickPlayer.name(),
                CoreAPI.getInstance().getGson().toJsonTree(new KickPlayerEvent(uuid, reason))
        ), false);
    }

    /**
     * 给玩家发送一条消息
     *
     * @param receiver 玩家
     * @param message  消息
     * @param cache    当玩家不在线时，是否缓存消息等待玩家上线再发送
     */
    public void sendMessageToPlayer(@NotNull UUID receiver, @NotNull DisplayMessage message, boolean cache) {
        sendMessageToPlayer(Collections.singleton(receiver), message, cache);
    }

    /**
     * 给玩家发送一条消息
     *
     * @param receivers 玩家
     * @param message   消息
     * @param cache     当玩家不在线时，是否缓存消息等待玩家上线再发送
     */
    public void sendMessageToPlayer(@NotNull Collection<UUID> receivers, @NotNull DisplayMessage message, boolean cache) {
        if (cache) {
            for (UUID receiver : receivers) {
                BallPlayerInfo info = getPlayerInfo(receiver);
                if (info != null && info.isOnline()) {
                    continue;
                }
                try (Connection connection = getDatasource().getConnection()) {
                    try (PreparedStatement statement = connection.prepareStatement(
                            "INSERT INTO `hamster_ball_cached_message` VALUES(?, ?);"
                    )) {
                        statement.setString(1, receiver.toString());
                        statement.setString(2, message.toJson().toString());
                        statement.executeUpdate();
                    }
                } catch (Exception e) {
                    BallAPI.getInstance().getLogger().log(Level.SEVERE, "更新玩家缓存消息时遇到了一个异常", e);
                }
            }
        }
        sendBallMessage(BALL_CHANNEL, new BallMessage(
                getLocalServerId(), null, BallServerType.PROXY,
                BallActions.SendMessageToPlayer.name(),
                CoreAPI.getInstance().getGson().toJsonTree(new SendMessageToPlayerEvent(new HashSet<>(receivers), message))
        ), false);
    }

    /**
     * 把玩家传送到一个位置
     * <p>
     * 如果目标位置不在当前服务器
     * <p>
     * 则会先尝试将玩家连接至目标服务器再进行传送
     *
     * @param sendPlayerUUID 玩家的uuid
     * @param location       坐标
     * @param doneMessage    传送完成后显示的消息
     */
    public void sendPlayerToLocation(@NotNull UUID sendPlayerUUID, @NotNull BallLocation location, @Nullable DisplayMessage doneMessage) {
        sendPlayerToLocation(Collections.singleton(sendPlayerUUID), location, doneMessage);
    }

    /**
     * 把玩家传送到一个位置
     * <p>
     * 如果目标位置不在当前服务器
     * <p>
     * 则会先尝试将玩家连接至目标服务器再进行传送
     *
     * @param sendPlayerUUID 玩家的uuid
     * @param location       坐标
     * @param doneMessage    传送完成后显示的消息
     */
    public void sendPlayerToLocation(@NotNull Collection<UUID> sendPlayerUUID, @NotNull BallLocation location, @Nullable DisplayMessage doneMessage) {
        sendRawBallMessage(BALL_CHANNEL, BallActions.SendPlayerToLocation.name(),
                new SendPlayerToLocationEvent(new HashSet<>(sendPlayerUUID), location, doneMessage)
        );
    }

    /**
     * 把玩家传送到另一个玩家身边
     * <p>
     * 支持跨服传送
     *
     * @param sendPlayer        被传送的玩家
     * @param toPlayer          传送的目标玩家
     * @param doneMessage       传送完成后显示的消息，自动将 %player_name% 替换成传送目标玩家的名称
     * @param doneTargetMessage 传送完成后目标玩家显示的消息，自动将 %player_name% 替换成被传送者的名称
     */
    public void sendPlayerToPlayer(@NotNull UUID sendPlayer, @NotNull UUID toPlayer, @Nullable DisplayMessage doneMessage, @Nullable DisplayMessage doneTargetMessage) {
        sendPlayerToPlayer(Collections.singleton(sendPlayer), toPlayer, doneMessage, doneTargetMessage);
    }

    /**
     * 把玩家传送到另一个玩家身边
     * <p>
     * 支持跨服传送
     *
     * @param sendPlayerUUID    被传送的玩家
     * @param toPlayer          传送的目标玩家
     * @param doneMessage       传送完成后显示的消息，自动将 %player_name% 替换成传送目标玩家的名称
     * @param doneTargetMessage 传送完成后目标玩家显示的消息，自动将 %player_name% 替换成被传送者的名称
     */
    public void sendPlayerToPlayer(@NotNull Collection<UUID> sendPlayerUUID, @NotNull UUID toPlayer, @Nullable DisplayMessage doneMessage, @Nullable DisplayMessage doneTargetMessage) {
        sendRawBallMessage(
                BALL_CHANNEL,
                BallActions.SendPlayerToPlayer.name(),
                new SendPlayerToPlayerEvent(new HashSet<>(sendPlayerUUID), toPlayer, doneMessage, doneTargetMessage)
        );
    }

    /**
     * 发送一条服务消息
     *
     * @param channel 消息频道
     * @param action  执行动作
     */
    public void sendBallMessage(@NotNull String channel, @NotNull String action) {
        sendBallMessage(channel, new BallMessage(action));
    }

    /**
     * 发送一条服务消息，不自动添加频道前缀
     *
     * @param channel 消息频道
     * @param action  执行动作
     */
    public void sendRawBallMessage(@NotNull String channel, @NotNull String action) {
        sendBallMessage(channel, new BallMessage(action), false);
    }

    /**
     * 发送一条有附加参数的服务消息
     *
     * @param channel 消息频道
     * @param action  执行动作
     * @param content 附加参数
     */
    public void sendBallMessage(@NotNull String channel, @NotNull String action, @NotNull Object content) {
        sendBallMessage(channel, new BallMessage(action, content));
    }

    /**
     * 发送一条有附加参数的服务消息，不自动添加频道前缀
     *
     * @param channel 消息频道
     * @param action  执行动作
     * @param content 附加参数
     */
    public void sendRawBallMessage(@NotNull String channel, @NotNull String action, @NotNull Object content) {
        sendBallMessage(channel, new BallMessage(action, content), false);
    }

    /**
     * 发送自定义消息
     *
     * @param message 消息内容
     */
    public void sendBallMessage(@NotNull String channel, @NotNull BallMessage message) {
        sendBallMessage(channel, message, true, false);
    }

    /**
     * 发送自定义消息
     *
     * @param message   消息内容
     * @param addPrefix 是否自动为消息频道添加分组前缀
     */
    public void sendBallMessage(@NotNull String channel, @NotNull BallMessage message, boolean addPrefix) {
        sendBallMessage(channel, message, addPrefix, false);
    }

    /**
     * 自定义服务消息信息并发送
     *
     * @param channel   消息频道
     * @param message   消息内容
     * @param addPrefix 是否自动为消息频道添加分组前缀
     * @param block     是否阻塞（设置为 true 则必须等待消息写入网络的操作完成后，该方法才会退出）
     */
    public void sendBallMessage(@NotNull String channel, @NotNull BallMessage message, boolean addPrefix, boolean block) {
        if (addPrefix) {
            channel = ballConfig.getChannelPrefix() + channel;
        }
        if (block) {
            try (Jedis jedis = CoreAPI.getInstance().getJedisPool().getResource()) {
                jedis.publish(channel, CoreAPI.getInstance().getGson().toJson(message));
            }
            eventBus.post(new MessageSentEvent(channel, message));
        } else {
            @NotNull String finalChannel = channel;
            CoreAPI.getInstance().getExecutorService().submit(() -> {
                try (Jedis jedis = CoreAPI.getInstance().getJedisPool().getResource()) {
                    jedis.publish(finalChannel, CoreAPI.getInstance().getGson().toJson(message));
                }
                eventBus.post(new MessageSentEvent(finalChannel, message));
            });
        }
    }

    /**
     * 订阅 redis 消息频道
     * <p>
     * 会自动加上 config 中设置的频道前缀
     *
     * @param channel 频道名称
     */
    public void subscribe(@NotNull String... channel) {
        for (int i = 0; i < channel.length; i++) {
            channel[i] = ballConfig.getChannelPrefix() + channel[i];
        }
        CoreAPI.getInstance().getExecutorService().submit(
                () -> redisSub.subscribe(BallRedisListener.INSTANCE, channel)
        );
    }

    /**
     * 忽略频道前缀配置，订阅 redis 消息频道
     *
     * @param channel 频道名称
     */
    public void subscribeIgnorePrefix(@NotNull String... channel) {
        CoreAPI.getInstance().getExecutorService().submit(
                () -> redisSub.subscribe(BallRedisListener.INSTANCE, channel)
        );
    }

    /**
     * 订阅 redis 消息频道（正则）
     *
     * @param patterns 频道名称正则表达式
     */
    public void subscribePatterns(@NotNull String patterns) {
        CoreAPI.getInstance().getExecutorService().submit(
                () -> redisSub.psubscribe(BallRedisListener.INSTANCE, patterns)
        );
    }

    /**
     * 取消订阅 redis 消息频道
     * <p>
     * 会自动加上 config 中设置的频道前缀
     *
     * @param channel 频道名称
     */
    public void unsubscribe(@NotNull String... channel) {
        for (int i = 0; i < channel.length; i++) {
            channel[i] = ballConfig.getChannelPrefix() + channel[i];
        }
        BallRedisListener.INSTANCE.unsubscribe(channel);
    }

    /**
     * 忽略频道前缀配置，取消订阅 redis 消息频道
     *
     * @param channel 频道名称
     */
    public void unsubscribeIgnorePrefix(@NotNull String... channel) {
        BallRedisListener.INSTANCE.unsubscribe(channel);
    }

    /**
     * 取消订阅 redis 消息频道（正则）
     *
     * @param patterns 频道名称正则表达式
     */
    public void unsubscribePatterns(@NotNull String patterns) {
        BallRedisListener.INSTANCE.punsubscribe(patterns);
    }

    @NotNull
    public String getLocalServerId() {
        return localServerInfo.getId();
    }

    /**
     * 获取服务器信息
     *
     * @param serverID 服务器ID
     * @return 可能为 null
     */
    public BallServerInfo getServerInfo(@NotNull String serverID) {
        return allServerInfo.get(serverID);
    }

    /**
     * 获取玩家信息
     *
     * @param uuid 玩家的 UUID
     * @return 玩家信息
     */
    public BallPlayerInfo getPlayerInfo(@NotNull UUID uuid) {
        return allPlayerInfo.get(uuid);
    }

    /**
     * 获取玩家信息（忽略大小写差异）
     *
     * @param playerName 玩家名称
     * @return 玩家信息
     */
    public BallPlayerInfo getPlayerInfo(@NotNull String playerName) {
        for (BallPlayerInfo info : allPlayerInfo.values()) {
            if (info.getName().equalsIgnoreCase(playerName)) {
                return info;
            }
        }
        return null;
    }

    /**
     * 获取玩家信息（大小写字母完全匹配）
     *
     * @param playerName 玩家名称
     * @return 玩家信息
     */
    public BallPlayerInfo getPlayerInfoExact(@NotNull String playerName) {
        for (BallPlayerInfo info : allPlayerInfo.values()) {
            if (info.getName().equals(playerName)) {
                return info;
            }
        }
        return null;
    }

    /**
     * 获取玩家的UUID
     *
     * @param playerName 玩家名称
     * @return 玩家的UUID
     */
    @Nullable
    public UUID getPlayerUUID(String playerName) {
        BallPlayerInfo info = getPlayerInfo(playerName);
        if (info == null) {
            return null;
        }
        return info.getUuid();
    }

    /**
     * 获取玩家的UUID
     *
     * @param playerName   玩家名称
     * @param defaultValue 如果未找到玩家信息则返回该值
     * @return 玩家的UUID
     */
    @NotNull
    public UUID getPlayerUUID(String playerName, @NotNull UUID defaultValue) {
        BallPlayerInfo info = getPlayerInfo(playerName);
        if (info == null) {
            return defaultValue;
        }
        return info.getUuid();
    }

    /**
     * 获取玩家名称
     *
     * @param uuid 玩家的 UUID
     * @return 如果数据不存在，则返回字符串形式的 "null"
     */
    @Nullable
    public String getPlayerName(@NotNull UUID uuid) {
        BallPlayerInfo info = getPlayerInfo(uuid);
        if (info == null) {
            return null;
        }
        return info.getName();
    }

    /**
     * 获取玩家名称
     *
     * @param uuid         玩家的 UUID
     * @param defaultValue 如果未找到玩家信息则返回该值
     * @return 玩家名称
     */
    @NotNull
    public String getPlayerName(@NotNull UUID uuid, @NotNull String defaultValue) {
        BallPlayerInfo info = getPlayerInfo(uuid);
        if (info == null) {
            return defaultValue;
        }
        return info.getName();
    }

    @NotNull
    public abstract Logger getLogger();
}
